/*
 * 
 * Copyright (C) 2010 Wilfried Elmenreich, Anita Sobe 
 * This file is part of VideoNetwork.
 * 
 * VideoNetwork is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 
 * You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. 
 */
package proxy;

import java.util.ArrayList;

import simulation.Simulator;

public class ShortestPathRouting {
	ArrayList<ServerNode> route = new ArrayList<ServerNode>();
	protected Simulator simulator;
	static final int maxHops = 5;

	public ShortestPathRouting(Simulator simulator){
		this.simulator=simulator;
	}
	
	private boolean recurseSearchRoute(ServerNode currentNode,
			int remainingDepth, int id) {
		// was this node already visited?
		if (route.contains(currentNode))
			return false;

		// add this node to the route
		route.add(currentNode);
		// have we found the unit?
		if (currentNode.searchPresentUnit(id) != null)
			return true;
		// maxDepth reached?
		if (remainingDepth == 0) {
			route.remove(currentNode);
			return false;
		}
		// search the adjacent nodes
		for (Link li : currentNode.getLinks())
			if (recurseSearchRoute(li.getTargetNode(), remainingDepth - 1, id))
				return true;
		// this was a dead end, remove this node from the route
		route.remove(currentNode);
		return false;
	}

	private ArrayList<ServerNode> searchRoute(ServerNode startingServer,
			int depth, int id) {
		route = new ArrayList<ServerNode>();
		if (recurseSearchRoute(startingServer, depth, id))
			return route;
		else
			return null;
	}

	public void routeUnit(ArrayList<ServerNode> route, int id) {
		if (route.size() == 1)
			return; // unit is already local
		String s, delim = "";
		s = "Routing u" + id;
		for (int i = route.size() - 1; i >= 0; i--) {
			s = s + delim + " S" + route.get(i).getId();
			delim = " ->";
		}
		simulator.log(s);
		
		ServerNode lastServer = route.get(route.size() - 1);
		boolean copy = false;
		UnitContainer uc = lastServer.searchPresentUnit(id);
		// if this unit is already routed somewhere, we must copy it
		if (uc.route != null)
			copy = true;
		// if this unit is pinned, we must copy it
		if (simulator.getSimTime() < uc.lastUse())
			copy = true;

		ServerNode nextServer = route.get(route.size() - 2);
		if (lastServer.migrateUnitTo(uc.getVideoUnit(),
				lastServer.getLinkTo(nextServer), copy)) {
			route.remove(lastServer);
			if (route.size() >= 2)
				nextServer.searchInIncomingUnits(id).route = route;
		}
	}

	double expectedTransferTime(ArrayList<ServerNode> route, int id) {
		double overallTime = 0.0;
		for (int i = route.size() - 1; i > 0; i--) {
			Link li = route.get(i).getLinkTo(route.get(i - 1));
			if (li == null)
				return 0.0;
			double occupiedTime = li.getOccupiedUntil()
					- simulator.getSimTime();
			if (occupiedTime < 0)
				occupiedTime = 0;
			overallTime += occupiedTime + VideoUnit.avgSize / li.getDatarate();
		}
		return overallTime;
	}

	public boolean fetchOnShortestRoute(ServerNode startingServer,
			int id) {
		// check if there is a fetch in progress for this id and this server
		if (((ShortestPathServerNode) startingServer).getFetchTimeOut()
				.containsKey(id)) {
			if (simulator.getSimTime() < ((ShortestPathServerNode) startingServer)
					.getFetchTimeOut().get(id)) {
				return false;
			} else {
				// the registered fetch process has timed out
				((ShortestPathServerNode) startingServer).getFetchTimeOut()
						.remove(id);
			}
		}

		ArrayList<ServerNode> route = null;
		for (int i = 1; i < maxHops; i++) {
			route = searchRoute(startingServer, i, id);
			if (route != null)
				break;
		}
		if (route == null) {
			simulator.log("Searching for u" + id + " from S"
					+ startingServer.getId() + " failed!");
			return false;
		}
		((ShortestPathServerNode) startingServer).getFetchTimeOut().put(id,
				expectedTransferTime(route, id));
		routeUnit(route, id);

		return true;
	}
}
